﻿using System;
using System.Buffers;
using System.Collections.Generic;
using System.Net;
using ePipe;
using Letter.IO.Binary;

namespace Letter.Net.Kcp;

sealed class SUdpConnection : AUdpConnection
{
    public SUdpConnection(KcpTransportOptions options, Func<AConnection> connectionFactory, Func<IPipe<byte>> channelProvider)
        : base(options, connectionFactory, channelProvider)
    {
    }

    private Dictionary<EndPoint, KcpSession> transportMap = new Dictionary<EndPoint, KcpSession>();

    protected override KcpSession GetTransport(EndPoint remoteEP)
    {
        if (this.transportMap.TryGetValue(remoteEP, out var transport))
        {
            return transport;
        }

        return null;
    }

    public override void OnActive()
    {
        base.OnActive();
    }

    protected override void OnMessage(EndPoint remoteEP, IPipeReader<byte> reader, ref ReadOnlySequence<byte> buf)
    {
        var bytesReader = buf.ToBytesReader();
        var flag = bytesReader.ReadUInt8();
        switch ((MessageType)flag)
        {
            case MessageType.SYN:
                if (bytesReader.ReadableLength != 4)
                {
                    return;
                }

                var connectId = bytesReader.ReadInt32BE();
                var conv = base.CreateConv(connectId);
                if (!this.transportMap.TryGetValue(remoteEP, out var transport))
                {
                    var sender = (IUdpSender) this;
                    var connection = this.ConnectionFactory();
                    transport = new KcpSession(connectId, conv, sender, connection, this.LocalEndPoint, remoteEP, this.Options, this.ChannelProvider);
                    transportMap.Add(remoteEP, transport);
                }

                if (transport.Conv != conv)
                {
                    return;
                }

                this.SendACK(remoteEP, connectId);

                break;
            case MessageType.ACK:
                break;
            case MessageType.K_MSG:
                this.RcvKcpMessage(remoteEP, ref bytesReader);
                break;
            case MessageType.U_MSG:
                break;
            case MessageType.FIN:
                break;
        }
        
        reader.AdvanceTo(bytesReader.End);
    }

    private void SendACK(EndPoint remoteEndPoint, int connectId)
    {
        var span = (stackalloc byte[4]);
        span.FWriteInt32BE(connectId);
        this.SendSpan(remoteEndPoint, MessageType.ACK, span);
    }
    
    public override void OnException(Exception ex)
    {
        foreach (var item in transportMap)
        {
            item.Value.OnUdpException(ex);
        }
    }
}